package com.tarry.io.file;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import de.innosystec.unrar.Archive;
import de.innosystec.unrar.NativeStorage;
import de.innosystec.unrar.exception.RarException;
import de.innosystec.unrar.rarfile.FileHeader;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class FileService {

    private final static String FILE_READ_PATH = "D:\\02_ebook\\";

    private final static String FILE_WRITE_PATH = "D:\\ebook_test\\ebook\\";

    private final static String FILE_TMP_PATH = "D:\\ebook_test\\tmp\\";

    private final static String EXCEL_WRITE_PATH =  "D:\\ebook_test\\汇总.xlsx";

    private final static List<String> ZIP_TYP = Arrays.asList("rar","zip");

    private final static List<String> EBOOK_TYPE =
            Arrays.asList("pdf","mobi","ebk2","ebk3","epub","doc","docx","azw3","prc","wpt","azw","chm","txt", "ebk3", "ebk2");
    /**
     *  读取指定目录，如果存在压缩文件，则解压，并将所有文件写入一个新的目录
     *
     */
    public static void main(String[] args) {
        new FileService().execute(FILE_READ_PATH);
    }

    public void execute(String path) {
        long start = System.currentTimeMillis();
        log.info("process start ~~~~~~~~~~~~~~~~~~~~~");
        readFile(path);
        // 写入excel
        writeExcel();
        log.info("process end ~~~~~~~~~~~~~~~~~~~~~");
        long end = System.currentTimeMillis();
        log.info("耗时：{} 秒 ", ( end - start) / 1000 / 60);
        boolean del = FileUtil.del(FILE_TMP_PATH);
        log.info("del tmp {}", del);
    }

    /**
     * 将所有文件名归档到excel文件中
     */
    private void writeExcel() {
        try {
            long start = System.currentTimeMillis();
            List<String> collect =
                    Files.list(Paths.get(FILE_WRITE_PATH)).map(path -> path.getFileName().toString()).collect(Collectors.toList());
            ExcelWriter writer = ExcelUtil.getWriter(EXCEL_WRITE_PATH);
            LinkedList<String> linkedList = new LinkedList<>(collect);
            linkedList.add(0, "书名");
            collect = new ArrayList<String>(linkedList);
            writer.write(collect);
            long end = System.currentTimeMillis();
            writer.close();
            log.info("写入excel耗时：{} 秒 ", ( end - start) / 1000);
        } catch (IOException e) {
            log.error("write excel fail ", e);
        }
    }

    public void readFile(String path) {

        try {
            Files.walkFileTree(Paths.get(path), new SimpleFileVisitor<Path>() {

                /**
                 *  访问文件夹
                 * @param dir
                 *          a reference to the directory
                 * @param attrs
                 *          the directory's basic attributes
                 *
                 * @return
                 * @throws IOException
                 */
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    log.info("dir: {}", dir);
                    return super.preVisitDirectory(dir, attrs);
                }

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    processFile(file);
                    return super.visitFile(file, attrs);
                }

                @Override
                public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                    return super.visitFileFailed(file, exc);
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    return super.postVisitDirectory(dir, exc);
                }
            });
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    private void processFile(Path file) {
        // 访问文件
        // 判断文件是不是压缩文件，如果是解压缩，如果不是，写入新的文件地址
        File tmpFile = file.toFile();
        // 获取后缀名
        String suffix = FileNameUtil.getSuffix(tmpFile);
        try {
            if (isZip(suffix)) {
                if (Objects.equals("zip", suffix.toLowerCase())) {
                    Path unZipFilePath = unZipFile(tmpFile);
                    readFile(unZipFilePath.toString());
                }else if (Objects.equals("rar", suffix.toLowerCase())) {
                    Path tmpPath = unRarFile(file.toFile());
                    readFile(tmpPath.toString());
                }else {
                    log.warn("unsupported zip type {} ~~~~~~~~~~~~~~~~~", suffix);
                }
            }else if (EBOOK_TYPE.contains(suffix.toLowerCase())){
                // 将文件写入指定目录
                writeFile(file.toFile());
            }else {
                log.error("不支持的类型: {} {}", tmpFile.getAbsoluteFile(), file.getFileName());
            }
        } catch (Exception e) {
            log.error("处理异常：", e);
        }
    }


    /**
     * 判断文件后缀名是否为压缩文件
     * @param fileSuffix 文件后缀
     * @return true or false
     */
    public synchronized boolean isZip(String fileSuffix) {
        return ZIP_TYP.contains(fileSuffix);
    }

    /**
     * 解压
     * @param file
     */
    public Path unZipFile(File file) {
        // 解压出来是个目录
        Path tempDirectory = null;
        try {
            tempDirectory = Files.createDirectories(Paths.get(FILE_TMP_PATH + System.currentTimeMillis()));
            // 解压
            ZipUtil.unzip(file,tempDirectory.toFile(),  Charset.forName("GBK"));
        }catch (Exception e){
            log.error("{} {}  unzip error:", file.getPath(), file.getName(),e);
        }
        return tempDirectory;
    }

    /**
     * 将rar压缩文件解压到临时目录，并返回目录地址
     * @param file file obj
     * @return file path
     */
    public Path unRarFile(File file) throws RuntimeException {
        Archive archive = null;
        FileOutputStream fileOutputStream = null;
        // 创建临时目录存放解压文件
        Path tempDirectory = null;
        try {

            tempDirectory = Files.createDirectories(Paths.get(FILE_TMP_PATH + System.currentTimeMillis()));
            archive = new Archive(new NativeStorage(file));

            FileHeader fileHeader = archive.nextFileHeader();

            while (Objects.nonNull(fileHeader)) {
                if ( !fileHeader.isDirectory() ) {

                    String compressFileName = fileHeader.getFileNameW();
                    // 解压文件名称
                    String destFileName = "";
                    String destDirName = "";

                    // 非windows系统
//                    if ("/".equals(File.separator)) {
//                        destFileName = tempDirectory.toString() + File.separator + compressFileName.replaceAll("\\\\", "/");
//                        destDirName = destFileName.substring(0, destFileName.lastIndexOf("/"));
//
//                    } else { // windows系统
//                        destFileName = tempDirectory.toString() +  File.separator + compressFileName.replaceAll("/", "\\\\");
//                        destDirName = destFileName.substring(0, destFileName.lastIndexOf("/"));
//                    }
                    destFileName = tempDirectory.toString() +  File.separator + compressFileName;
                    destDirName = destFileName.substring(0, destFileName.lastIndexOf(File.separator));

                    Path destDirPath = Paths.get(destDirName);
                    if (!destDirPath.toFile().exists()) {
                        Files.createDirectories(destDirPath);
                    }
                    // 解压缩文件
                    fileOutputStream = new FileOutputStream(destFileName);
                    archive.extractFile(fileHeader, fileOutputStream);
                    fileOutputStream.close();
                    fileOutputStream = null;
                }
                fileHeader = archive.nextFileHeader();
            }

        } catch (RarException | IOException e) {
            log.error("{} unzip rar error:",file.getPath(), e);
        } finally {
            if (Objects.nonNull(fileOutputStream)) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }

            if (Objects.nonNull(archive)){
                try {
                    archive.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return tempDirectory;
    }


    /**
     * 将文件写入指定文件夹
     * @param file
     */
    public void writeFile(File file) {
        String newPath = FILE_WRITE_PATH + new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
        try (FileInputStream source = new FileInputStream(file);
             FileOutputStream target = new FileOutputStream(newPath)) {
            FileChannel sourceChannel = source.getChannel();
            target.getChannel().transferFrom(sourceChannel, 0 , sourceChannel.size());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}
